package entity;

import java.util.Date;
import java.util.LinkedList;

import client.Client;

import entity.ParkingSpot.pSpot;

public class ParkingLot {
	private int parking_lot_number;
	private String parking_lot_name;
	private int num_of_parking_spots;	//the total number of free/in use parking spots  
	
	private int cols;
	private ParkingSpot parking_spots[][][];			//[depth][hight][length] or  [depth][store][col]
	
	private LinkedList <OrderListNode> orders=new LinkedList <OrderListNode>();	//list of orders
	
	public ParkingLot()
	{
	}
	
	
	
	
	public boolean createParkingLot(int depth,int hight,int length)	//initialize the parking lot
	{
		if (depth<=0 || hight<=0 || length<=0)
			return false;
		this.cols=length;
		parking_spots=new ParkingSpot[depth][hight][length];
		for (int i=0;i<depth;i++)
			for (int j=0;j<hight;j++)
				for (int k=0;k<length;k++)
					parking_spots[i][j][k]=new ParkingSpot();
		num_of_parking_spots=depth*hight*length;
		Client.UpdateParkingLotToDB(this);
		return true;
	}
	
	
	@SuppressWarnings("deprecation")
	//add order to the order list
	public boolean addOrder(Date start,Date finish)	//return true if the order received or false if theres no parking spot available or wrong input
	{
		Date begin=(Date) start.clone();
		Date end=(Date) finish.clone();
		int beginIndex=-1,endIndex=-1;
		begin.setSeconds(0);
		begin.setMinutes(begin.getMinutes()-begin.getMinutes()%15);	//round the time to full time units (15 minutes)
		end.setSeconds(0);
		end.setMinutes(end.getMinutes()-end.getMinutes()%15);	//round the time to full time units (15 minutes)
		int timeUnits=(int) ((end.getTime()-begin.getTime())/(1000*60*15));	//each time unit is exactly 15 minutes
		if (timeUnits<0)
			return false;
		end.setTime(end.getTime()-1000*60*15);	//the beginning of the end node = the end of the node minus 15 minutes
		
		for (int i=0; i<orders.size(); i++)	//go over the list and find the index of the beginning parking hour node and the index of the finish parking hour node (if they don't exist, create the nodes) 
		{
			//if (orders.get(i).getBegin().compareTo(begin)==0)
			if (orders.get(i).getBegin().getTime()/(1000*60)-begin.getTime()/(1000*60) ==0)	//same minute
				beginIndex=i;
			if (orders.get(i).getBegin().compareTo(begin) > 0 && beginIndex==-1)
			{
				orders.add(i,new OrderListNode(begin));
				beginIndex=i;
			}
			
			//if (orders.get(i).getBegin().compareTo(end)==0)
			if (orders.get(i).getBegin().getTime()/(1000*60)-end.getTime()/(1000*60) ==0)	//same minute
				endIndex=i;
			if (orders.get(i).getBegin().compareTo(end) > 0 && endIndex==-1)
			{
				orders.add(i,new OrderListNode(end));
				endIndex=i;
			}
		}
		if (beginIndex==-1)	//the begin index is after the last node
		{
			orders.add(new OrderListNode(begin));
			beginIndex=orders.size()-1;
		}
		if (endIndex==-1)	//the end index is after the last node
		{
			orders.add(new OrderListNode(end));
			endIndex=orders.size()-1;
		}
		//System.out.println(beginIndex + " " + endIndex);
		
		// check if there's an available parking spot during the whole time   AND     constructing missing nodes between begin Parking and end parking
		if (orders.get(endIndex).getTaken_spots()+1 > this.num_of_parking_spots)
			return false;
		Date temp;
		for (int i=beginIndex; i<endIndex; i++)		
		{
			if (orders.get(i).getTaken_spots()+1 > this.num_of_parking_spots)
				return false;
			//System.out.println(orders.get(i+1).getBegin().getTime()/(1000*60) + "  " + orders.get(i).getBegin().getTime()/(1000*60)+ "  " + (orders.get(i+1).getBegin().getTime()-orders.get(i).getBegin().getTime() - 1000*60*15));
			if ((orders.get(i+1).getBegin().getTime()-orders.get(i).getBegin().getTime())/(1000*60) > 15)
			{
				temp=(Date)orders.get(i).getBegin().clone();
				temp.setTime(temp.getTime()+1000*60*15);
				orders.add(i+1,new OrderListNode(temp));
				endIndex++;	//added new node before the end index so the end index get higher
			}
		}
		
		//take 1 more spot for each time unit between the begin date to the end date
		for (int i=beginIndex; i<=endIndex; i++)	
		{
			orders.get(i).setTaken_spots(orders.get(i).getTaken_spots()+1);
		}
		Client.UpdateParkingLotToDB(this);
		return true;
	}
	
	
	public boolean removeOrder(Date start,Date finish)
	{
		Date begin=(Date) start.clone();
		Date end=(Date) finish.clone();
		int beginIndex=-1,endIndex=-1;
		end.setTime(end.getTime()-1000*60*15);	//the beginning of the end node = the end of the node minus 15 minutes
		
		for (int i=0; i<orders.size(); i++)
		{
			//if (orders.get(i).getBegin().compareTo(begin)==0)
			if (orders.get(i).getBegin().getTime()/(1000*60)-begin.getTime()/(1000*60) ==0)	//same minute
				beginIndex=i;
			
			//if (orders.get(i).getBegin().compareTo(end)==0)
			if (orders.get(i).getBegin().getTime()/(1000*60)-end.getTime()/(1000*60) ==0)	//same minute
				endIndex=i;
		}
		if (endIndex==-1 || beginIndex==-1)
			return false;
		
		if (orders.get(endIndex).getTaken_spots() <= 0)
			return false;
		for (int i=beginIndex; i<endIndex; i++)		
		{
			if (orders.get(i).getTaken_spots() <= 0)
				return false;
			//System.out.println(orders.get(i+1).getBegin().getTime()/(1000*60) + "  " + orders.get(i).getBegin().getTime()/(1000*60)+ "  " + (orders.get(i+1).getBegin().getTime()-orders.get(i).getBegin().getTime() - 1000*60*15));
			if ((orders.get(i+1).getBegin().getTime()-orders.get(i).getBegin().getTime())/(1000*60) > 15)
				return false;
		}
		
		//the input test finished- now free spots
		for (int i=beginIndex; i<=endIndex; i++)	
		{
			orders.get(i).setTaken_spots(orders.get(i).getTaken_spots()-1);
		}
		cleanList();	//clean nodes which contain 0 orders
		Client.UpdateParkingLotToDB(this);
		return true;
	}
	
	
	
	
	private void cleanList()
	{
		for(int i=orders.size()-1;i>=0; i--)
		{
			if (orders.get(i).getTaken_spots()==0)
				orders.remove(i);
				
		}
	}
	
	public boolean insertCar(String carNumber, Date endParking)
	{
		
		int index=3*this.cols;
		int store=-1;
		boolean fullStoreFlag=true;
		for (int i=0;i<3; i++)	//go over 3 stores (komot)
		{
			fullStoreFlag=true;
			for (int j=0; j<3*this.cols; j++)	//check if the store is full
			{
				if (this.parking_spots[getIJ(j)[0]][i][getIJ(j)[1]].getStatus()==pSpot.FREE)	//go over the store by distance index (see private functions)
				{
					fullStoreFlag=false;
					break;
				}
			}
			if(!fullStoreFlag)	//if it isnt full
			{
				for (int j=0; j<3*this.cols; j++)	//go over the store by distance index and check where to insert the car (the car with the closest endParking hour should be in the shortest distance- the lowest index )
				{
					//if the spot is free
					if(this.parking_spots[getIJ(j)[0]][i][getIJ(j)[1]].getStatus()==pSpot.FREE && index>j)	//the index check is to see if its better than the prev store
					{
						index=j;
						store=i;
						break;
					}
					//if the spot is taken
					else if(this.parking_spots[getIJ(j)[0]][i][getIJ(j)[1]].getStatus()==pSpot.IN_USE)
						//take the spot if the ending hour is lower than the car in this spot 
						if (this.parking_spots[getIJ(j)[0]][i][getIJ(j)[1]].getEndParkTime().compareTo(endParking) > 0 && (index>j || (index==j && this.parking_spots[getIJ(index)[0]][store][getIJ(index)[1]].getStatus()==pSpot.IN_USE && this.parking_spots[getIJ(j)[0]][i][getIJ(j)[1]].getEndParkTime().compareTo(this.parking_spots[getIJ(index)[0]][store][getIJ(index)[1]].getEndParkTime())>0)))
							{
								index=j;
								store=i;
								break;
							}
						
				}
			}
		}	//now we know the store and index in the store which the car should be in
		if (store==-1)	//it means all the stores are full
			return false;
		ParkingSpot ps=new ParkingSpot();	//used to move each car to the next index
		ps.setStatus(pSpot.IN_USE);
		ps.setCarNumber(carNumber);
		ps.setEndParkTime(endParking);
		ParkingSpot temp;	//used to swich car spots
		for (int j=index; j<3*this.cols; j++)	//start at the distance index- the first car to insert is the new one
		{										//switch each car to the next index untill we reach a free spot
			while (this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getStatus() == pSpot.DISABLED ||
					this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getStatus() == pSpot.SAVED )
				j++;	//skip disabled or saved spots
			if (this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getStatus() == pSpot.FREE)	//we reached a free spot, place there ps and finish the process
			{
				this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]]=ps;
				break;
			}
			else	//place ps in this index and save the car that was there to ps
			{
				temp=ps;
				ps=this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]];
				this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]]=temp;
			}
					
		}
		Client.UpdateParkingLotToDB(this);	//need to update the insert on DB
		return true;
	}
	
	
	public boolean popCar(String carNumber)
	{
		int ii = -1,jj = -1,kk=-1;	//first of all find the car
		for (int i=0; i<3; i++)
			for (int j=0; j<3; j++)
				for (int k=0; k<this.cols; k++)
						if (this.parking_spots[i][j][k].getCarNumber().compareTo(carNumber)==0)
						{
							ii=i;
							jj=j;
							kk=k;
						}
		if (kk==-1)		//the car weren't found
			return false;
		int index=this.getDistanceIndex(ii, kk);	//get the distance index and store of the car
		int store=jj;
		ParkingSpot ps=new ParkingSpot();
		ParkingSpot temp;
		//this.parking_spots[getIJ(3*this.cols-1)[0]][store][getIJ(3*this.cols-1)[1]]=new ParkingSpot();
		for (int j=3*this.cols-1; j>=index; j--)	//like the insert, we pop
		{
			while (this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getStatus() == pSpot.DISABLED ||
					this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getStatus() == pSpot.SAVED )
				j--;
			if (this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getStatus()==pSpot.IN_USE && this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]].getCarNumber().compareTo(carNumber)==0)
			{
				this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]]=ps;
				break;
			}
			else
			{
				temp=ps;
				ps=this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]];
				this.parking_spots[getIJ(j)[0]][store][getIJ(j)[1]]=temp;
			}
					
		}
		Client.UpdateParkingLotToDB(this);// save the change to DB
		return true;
	}
	
	
	public boolean disableSpot(int depth, int store, int col)
	{
		if (this.parking_spots[depth][store][col].getStatus() != pSpot.FREE)
			return false;
		this.parking_spots[depth][store][col].setStatus(pSpot.DISABLED);
		Client.UpdateParkingLotToDB(this);// save the change to DB
		return true;
	}
	
	public boolean saveSpot(int depth, int store, int col)
	{
		if (this.parking_spots[depth][store][col].getStatus() != pSpot.FREE)
			return false;
		this.parking_spots[depth][store][col].setStatus(pSpot.SAVED);
		Client.UpdateParkingLotToDB(this);
		return true;
	}
	
	public boolean activateSpot(int depth, int store, int col)
	{
		this.parking_spots[depth][store][col].setStatus(pSpot.SAVED);
		Client.UpdateParkingLotToDB(this);
		return true;
	}
	
	
	private int getDistanceIndex(int i,int j)	//every spot in the store has distanceIndex, the lower index mean lower distance from the exit spot 
	{											//this function calculate the distance index using the place in the store
		if (i>=3 || j>=cols || i<0 || j<0)		//this function can be used as a black box, it was tested hundred times 
			return -1;
		int ii=-1;
		int jj=1;
		int dis=0;
		int index=-1;
		while(!(i==ii && j==jj))
		{
			if (ii==2 || jj==0)
			{
				dis++;
				ii=0;
				jj=dis;
			}
			else
			{
				jj--;
				ii++;
			}
			while (jj>=this.cols)
			{
					jj--;
					ii++;
			}
			index++;
			
		}
		return index;
		
		
	}
	
	private int[] getIJ (int distanceIndex)	//this function calculate the place in the store using the distance index.
	{										//this function can be used as a black box, it was tested hundred times 
		int i=-1;
		int j=1;
		int dis=0;
		if(distanceIndex>=3*cols)
			return null;
		for (int k=0;k<=distanceIndex; k++)
		{
			if (i==2 || j==0)
			{
				dis++;
				i=0;
				j=dis;
			}
			else
			{
				j--;
				i++;
			}
			while (j>=this.cols)
			{
					j--;
					i++;
			}
		}
		int[] ret=new int[2];
		ret[0]=i;
		ret[1]=j;
		return ret;
	}
	
	
	public String getParking_lot_name() {
		return parking_lot_name;
	}
	public void setParking_lot_name(String parking_lot_name) {
		this.parking_lot_name = parking_lot_name;
	}
	public int getParking_lot_number() {
		return parking_lot_number;
	}
	public void setParking_lot_number(int parking_lot_number) {
		this.parking_lot_number = parking_lot_number;
	}
	public int getNum_of_parking_spots() {
		return num_of_parking_spots;
	}
	public void setNum_of_parking_spots(int num_of_parking_spots) {
		this.num_of_parking_spots = num_of_parking_spots;
	}
	
	public ParkingSpot[][][] getParking_spots()
	{
		return parking_spots;
	}
	
	public void printOrders()
	{
		if (orders.size()==0)
			System.out.println("no orders");
		else
			for(int i=0;i<orders.size();i++)
			{
				System.out.println(orders.get(i).getBegin() + "     taken spots: " +orders.get(i).getTaken_spots());
			}
	}
	public String toString()
	{
		String s="";
		for (int i=0; i<3;i++)
		{
			s+="\n\n\nSTORE "+i+": \n";
			for (int j=0; j<3;j++)
			{
				s+="\n";
				for (int k=0; k<4;k++)
				{
					if (this.parking_spots[j][i][k].getStatus()==pSpot.IN_USE)
						s+=this.parking_spots[j][i][k].getCarNumber() + "  ";
					else if (this.parking_spots[j][i][k].getStatus()==pSpot.FREE)
						s+="FREE   ";
					else if (this.parking_spots[j][i][k].getStatus()==pSpot.SAVED)
						s+="SAVED   ";
					else if (this.parking_spots[j][i][k].getStatus()==pSpot.DISABLED)
						s+="DISABLED   ";
				}
			}
		}
		return s;
	}




	public int getCols() {
		// TODO Auto-generated method stub
		return cols;
	}
}
